// Author	: damphat@gmail.com
// Created	: 01/12/2008

#pragma once
#include "vn/IOCommon.h"
#include "vn/Stream.h"
#include "vn/Exception.h"
#include "vn/Math.hpp"
#include "vn/Mem.h"
#include <windows.h>


#define CHECK_IO_GLOBAL_API(condition)\
	if(!(condition)) {\
		IOException e(#condition);\
		throw e;\
	}


IO_BEGIN

class HGlobalStream : public Stream {
public:
	HGlobalStream(size_t initSize = 0) {
		CHECK_ARG(initSize >= 0);	// faltal
		m_hGlobal = GlobalAlloc(GHND, initSize);
		m_deleteOnClose = true;
		m_pos = 0;
	}

	HGlobalStream(HGLOBAL hGlobal, bool deleteOnClose = false) {
		CHECK_ARG(hGlobal != NULL);	// faltal
		m_hGlobal = hGlobal;
		m_deleteOnClose = deleteOnClose;
		m_pos = 0;
	}

	virtual StmLen Read(Buf8 *buf, StmLen len) {
		CHECK_ARG_BUFFER(buf, len);
		CHECK_STATUS(m_hGlobal);
		Buf8 *ptr = (Buf8 *)GlobalLock(m_hGlobal);
		StmLen remain = Math::Max(GetLength() - m_pos, 0LL);
		StmLen rtnsiz = Math::Min(remain, len);
		Mem::Copy(buf, ptr + m_pos, rtnsiz);
		GlobalUnlock(m_hGlobal);
		m_pos += rtnsiz;
		return rtnsiz;
	}

	virtual void Write(const Buf8 *buf, StmLen len) {
		CHECK_ARG_BUFFER(buf, len);
		CHECK_STATUS(m_hGlobal);

		// ensure having len bytes after stream position
		Math::Max(0, 0);
		StmLen remain = Math::Max(GetLength() - m_pos, 0);
		if (remain < len) {
			SetLength(m_pos + len);
			remain = len;
		}

		// copy data into buffer
		Buf8 *ptr = (Buf8 *)GlobalLock(m_hGlobal);
		Mem::Copy(ptr + m_pos, buf, (size_t)len);
		GlobalUnlock(m_hGlobal);

		// change stream pointer
		m_pos += len;
	}

	virtual StmPos Seek(StmOff offset, SeekOrigin origin) {
		CHECK_STATUS(m_hGlobal);
		switch (origin) {
		case SeekBegin:
			CHECK_ARG(offset >= 0);
			m_pos = offset;
			break;
		case SeekCurrent:
			CHECK_ARG(m_pos + offset >= 0);
			m_pos += offset;
			break;
		case SeekEnd:
			CHECK_ARG(GetLength() + offset >= 0);
			m_pos = GetLength() + offset;
			break;
		}
		return m_pos;
	}

	virtual void Flush() {
		CHECK_STATUS(m_hGlobal);
	}

	virtual void Close() {
		if(m_deleteOnClose) {
			if (m_hGlobal) {
				HGLOBAL temp = GlobalFree(m_hGlobal);
				CHECK_IO_GLOBAL_API(temp == NULL);
				m_hGlobal = 0;
			}
		}
	}

	virtual bool CanRead() {
		return m_hGlobal != 0;
	}

	virtual bool CanWrite() {
		return m_hGlobal != 0;
	}

	virtual bool CanSeek() {
		return m_hGlobal != 0;
	}

	virtual StmPos GetPosition() {
		CHECK_STATUS(m_hGlobal);
		return m_pos;
	}

	virtual void SetPosition(StmPos pos) {
		CHECK_ARG(pos >= 0);
		CHECK_STATUS(m_hGlobal);
		m_pos = pos;
	}

	virtual StmLen GetLength() {
		CHECK_STATUS(m_hGlobal);
		SetLastError(S_OK);
		SIZE_T size = GlobalSize(m_hGlobal);
		if (size == 0) {
			HRESULT lastError = GetLastError();
			CHECK_IO_GLOBAL_API(lastError == S_OK);
		}
		return size;
	}

	virtual void SetLength(StmLen len) {
		CHECK_ARG(len >= 0)
		CHECK_STATUS(m_hGlobal);
		HGLOBAL temp = GlobalReAlloc(m_hGlobal, (size_t)len, GHND);
		CHECK_IO_GLOBAL_API(temp != NULL);
		m_hGlobal = temp;
	}

	virtual ~HGlobalStream() {
		Close();
	}

public:
	HGLOBAL m_hGlobal;
	bool	m_deleteOnClose;
	StmPos	m_pos;
};

IO_END
